package sim;

import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import obj.Edge;
import obj.Node;
import ui.MainUI;
import ui.VehicleQueueReportPanel;
import util.Util;

public class VehicleRunnable implements Runnable, Observer {

	private Vehicle vehicle;
	private Component component;
	public static final int DELAY = 400;
	private double distance = 0;
	private volatile boolean paused;
	private Node[][] nodesMatrix;
	private static int lightCycle;
	public Map<Edge, Set<String>> carsInEdgesMap;

	public VehicleRunnable(Vehicle aVehicle, Component aComponent, Node[][] matrix, Map<Edge, Set<String>> map) {
		this.vehicle = aVehicle;
		this.component = aComponent;
		this.nodesMatrix = matrix;
		this.carsInEdgesMap = map;
	}

	public static void updateLightCycle(int cycle){
		lightCycle = cycle;
	}
	@Override 
	public void run() {
		try {
			while (!this.vehicle.getCurrentPosition().equals(pathEndPoint)) {
				while (paused) {
					Thread.sleep(DELAY);
				}
				MainUI.lblTimeLable.setText(MainUI.stopWatch.toString());
				if (this.vehicle.getLeadVehicle() != null
						&& this.vehicle.getLeadVehicle().getCurrentPosition().equals(pathEndPoint)) {
					this.vehicle.setLeadVehicle(null);
				}
				Point2D.Double point = getNextPosition(this.vehicle);
				this.vehicle.move(point);
				VehicleQueueReportPanel.refreshTable(carsInEdgesMap, lightCycle);
				component.repaint();
				Thread.sleep(DELAY);
			}

			vehicle.getPath().setVehiclesExited(vehicle);

		} catch (InterruptedException e) {

		}

	}

	private int edgePosition = 0;
	private int count = 0;
	private Point2D.Double sPoint = null;
	private Point2D.Double pathEndPoint = null;
	private Point2D.Double ePoint;

	/**
	 * The method gives the next position in the path where the vehicle should
	 * be in depending on its current position.
	 * 
	 * @param vehicle
	 * @return Point2D.Double
	 */
	private Point2D.Double getNextPosition(Vehicle vehicle) {

		double currSpeed = implementCarFollowingModel(vehicle);
		vehicle.setCurrentSpeed(currSpeed);

		/* check traffic light status */
		Point2D.Double currPoint = vehicle.getCurrentPosition();
		Node n = nodesMatrix[new Double(currPoint.getX()).intValue() / 10][new Double(currPoint.getY()).intValue() / 10];

		if (n != null && n.isLight()) {
			if (n.isVehicleIn() && Node.STATUS_RED.equals(n.getLightState())) {
				vehicle.setCurrentSpeed(0);
			}
		}

		/* set color of vehicle */
		vehicle = setVehicleColor(vehicle);
		distance = getDistance(vehicle);

		Point2D.Double nextPosition = null;
		boolean isNewEdge = false;
		if (sPoint != null && sPoint.equals(ePoint)) {
			edgePosition++;
			isNewEdge = true;
		}
		if (sPoint != null && sPoint.distance(ePoint) <= distance && !isNewEdge) {
			nextPosition = ePoint;
			sPoint = ePoint;
			return nextPosition;
		}

		List<Edge> edgesList = vehicle.getPath().getEdgesList();
		Point pathStartPoint = new Point(edgesList.get(edgePosition).getStartNode().getxPoint(), edgesList
				.get(edgePosition).getStartNode().getyPoint());
		pathEndPoint = new Point2D.Double(edgesList.get(edgesList.size() - 1).getEndNode().getxPoint(), edgesList
				.get(edgesList.size() - 1).getEndNode().getyPoint());

		Edge e = edgesList.get(edgePosition);
		/* Add previous edge */
		if (vehicle.getCurrentEdge() != null) {
			vehicle.setPrevEdge(vehicle.getCurrentEdge());
		}

		/* Add current Edge */
		vehicle.setCurrentEdge(e);

		/* Add car to the edge */
		Set carSet = carsInEdgesMap.get(e);
		if (carSet != null) {
			if (vehicle.getCurrentSpeed() <= 0) {
				carSet.add(vehicle.getName());
			}

		} else {
			carSet = new HashSet<String>();
			if (vehicle.getCurrentSpeed() <= 0) {
				carSet.add(vehicle.getName());
			}
			carsInEdgesMap.put(e, carSet);
		}
		/* remove vehicle from other edges */
		if (vehicle.getPrevEdge() != null && !vehicle.getPrevEdge().equals(vehicle.getCurrentEdge())) {
			carsInEdgesMap.get(vehicle.getPrevEdge()).remove(vehicle.getName());
//			if(vehicle.getPrevEdge().getName().equals("e9")){
//				System.out.print(vehicle.getName()+" removed : set size");
//				System.out.println(carsInEdgesMap.get(vehicle.getPrevEdge()).size());
//			}
		}

		if (count == 0) {
			sPoint = new Point2D.Double(e.getStartNode().getxDouble(), e.getStartNode().getyDouble());
			ePoint = new Point2D.Double(e.getEndNode().getxDouble(), e.getEndNode().getyDouble());
			vehicle.setCarAngle(Util.getAngle(sPoint, ePoint));
		}

		ePoint = new Point2D.Double(e.getEndNode().getxDouble(), e.getEndNode().getyDouble());
		nextPosition = Util.getNextPoint(sPoint, ePoint, distance);
		vehicle.setCarAngle(Util.getAngle(sPoint, ePoint));
		sPoint = nextPosition;
		count++;
		return nextPosition;
	}

	/**
	 * implementation of the simple car following model
	 * 
	 * @param Vehicle
	 *            v
	 * @return currentSpeed
	 */
	private double implementCarFollowingModel(Vehicle v) {

		if (v.getLeadVehicle() != null) {

			double distanceFromLeader = getDistanceFromLeader(v);
			// distanceToStop = Distance which the follower should stop
			double distanceToStop = 3;
			/*
			 * lengthToSlowDown = length in which the follower should begin to
			 * slow down
			 */
			double lengthToSlowDown = getDistance(v) * 3;

			/*
			 * v = mx + c : distanceToStop < distanceFromLeader <
			 * lengthToSlowDown, v = u : distanceFromLeader >=lengthToSlowDown,
			 * v = 0 : distanceFromLeader <= distanceToStop.
			 */
			double maxSpeed = v.getMaxSpeed();

			double gradient = maxSpeed / (lengthToSlowDown - distanceToStop);
			double intercept = (maxSpeed * distanceToStop) / (distanceToStop - lengthToSlowDown);

			double currentSpeed = v.getCurrentSpeed();

			if (distanceFromLeader <= distanceToStop) {
				currentSpeed = 0;
			} else if (distanceFromLeader < lengthToSlowDown) {
				currentSpeed = gradient * distanceFromLeader + intercept;
			} else if (currentSpeed == 0) {
				currentSpeed = v.getPath().getMaxSpeed();
			}

			/*
			 * if the vehicle and its leader does not belong to the same edge
			 * then set the vehicle speed to its max
			 */
			if (v.getCurrentEdge() != null && v.getLeadVehicle().getCurrentEdge() != null) {
				double currSpeed = 0;
				double pathMaxSpeed = v.getPath().getMaxSpeed();
				if (!v.getCurrentEdge().equals(v.getLeadVehicle().getCurrentEdge())
						&& v.getLeadVehicle().getCurrentSpeed() > 0) {

					currSpeed = pathMaxSpeed;

					return currSpeed;
				} else {
					if (v.getLeadVehicle().getCurrentSpeed() > 0
							&& v.getLeadVehicle().getCurrentSpeed() == pathMaxSpeed
							&& distanceFromLeader > lengthToSlowDown) {
						currSpeed = pathMaxSpeed;
						return currSpeed;
					}
				}
			}
			return currentSpeed;
		} else {
			double currentSpeed = v.getPath().getMaxSpeed();
			return currentSpeed;
		}
	}

	/**
	 * The method returns the distance in pixels between the leading and the
	 * following car
	 * 
	 * @param Vehicle
	 *            v
	 * @return double distance from leader
	 */
	private double getDistanceFromLeader(Vehicle v) {
		double distance = 0;

		double dx = v.getLeadVehicle().getX() - (v.getX() + (Car.CAR_WIDTH * Math.cos(v.getCarAngle())));
		double dy = v.getLeadVehicle().getY() - (v.getY() - (Car.CAR_WIDTH * Math.sin(v.getCarAngle())));
		distance = Math.sqrt(dx * dx + dy * dy);
		return distance;
	}

	@Override
	public void notify(boolean state) {
		this.paused = state;
	}

	/**
	 * Returns the Distance in pixel value for a given speed
	 * 
	 * @param Vehicle
	 *            v
	 * @return double pixels
	 */

	private double getDistance(Vehicle v) {
		double distanceInPixels = 0;
		double mps = Util.kmphToMetersPerSec(v.getCurrentSpeed());
		double distanceInMeters = Util.mpsToDistance(mps);
		distanceInPixels = Util.distanceToPixels(distanceInMeters);
		return distanceInPixels;
	}

	/**
	 * Set the vehicle color depending on its current speed
	 * 
	 * @param Vehicle
	 *            v
	 * @return Vehicle v
	 */
	private Vehicle setVehicleColor(Vehicle v) {

		double currSpeed = v.getCurrentSpeed();
		if (currSpeed > 60) {
			v.setColor(Color.BLACK);
		} else if (51 <= currSpeed && currSpeed <= 60) {
			v.setColor(new Color(0, 51, 102));
		} else if (41 <= currSpeed && currSpeed <= 50) {
			v.setColor(new Color(0, 51, 255));
		} else if (31 <= currSpeed && currSpeed <= 40) {
			v.setColor(new Color(0, 153, 255));
		} else if (21 <= currSpeed && currSpeed <= 30) {
			v.setColor(new Color(0, 204, 255));
		} else if (11 <= currSpeed && currSpeed <= 20) {
			v.setColor(new Color(153, 204, 255));
		} else if (1 <= currSpeed && currSpeed <= 10) {
			v.setColor(new Color(204, 204, 255));
		} else if (0 == currSpeed || currSpeed < 0) {
			v.setColor(Color.WHITE);
		}

		return v;
	}
}
